﻿
package extremefx.ui {
	import extremefx.events.EventHandler;
	import extremefx.events.KeyEventArgs;
	
	import flash.events.Event;		

	/**
	 * @author Marcelo Volmaro
	 */
	public final class KeyComboHandler {
		private static var _instance:KeyComboHandler;
		private static var _keysTyped:Vector.<uint> = new Vector.<uint>();
		private static var _combos:Vector.<KeyCombo> = new Vector.<KeyCombo>();
		private static var _combosDown:Vector.<KeyCombo> = new Vector.<KeyCombo>();
		private static var _longestCombo:uint = 0;
		
		public static function init():void {
			if (!_instance){
				_instance = new KeyComboHandler();
				KeyHandler.onKeyDown.add(_instance._keyPressed);
				KeyHandler.onKeyUp.add(_instance._keyReleased);
				UIStage.STAGE.addEventListener(Event.DEACTIVATE, _instance._onDeactivate);
			}
		}
		
		private function _keyPressed(pSender:KeyHandler, pArgs:KeyEventArgs):void {
			var kc:uint = pArgs.keyboardEvent.keyCode;
			var alreadyDown:Boolean = pArgs.alreadyDown; 

			_keysTyped.push(kc);
			
			if (_keysTyped.length > _longestCombo)
				_keysTyped.splice(0, 1);
			
			var l:uint = _combos.length;
			while (l--) {
				_checkedTypedKeys(_combos[l]);
				
				if (!alreadyDown)
					_checkDownKeys(_combos[l]);
			}
		}
		
		private function _keyReleased(pSender:KeyHandler, pArgs:KeyEventArgs):void {
			var kc:uint = pArgs.keyboardEvent.keyCode;
			var l:uint = _combosDown.length;
			
			while (l--) {
				if (_combosDown[l].keyCodes.indexOf(kc) != -1) {
				
					_combosDown.splice(l, 1);
					if (_eOnComboUp) _eOnComboUp.fire(new KeyComboEventArgs(_combosDown[l]));
				}
			} 
		}
		
		private function _checkedTypedKeys(keyCombo:KeyCombo):void {
			
			if (keyCombo.compares(_keysTyped.slice(-keyCombo.keyCodes.length))) {
				if (_eOnComboSequence) _eOnComboSequence.fire(new KeyComboEventArgs(keyCombo));
			}
		}
		
		protected function _checkDownKeys(keyCombo:KeyCombo):void {
			var uniqueCombo:Vector.<uint> = keyCombo.keyCodes.filter(function (e:*, i:int, inArray:Array):Boolean {
				return (i == 0) ? true : inArray.lastIndexOf(e, i - 1) == -1;
			}), 
			
			i:uint = uniqueCombo.length;
			
			while (i--)
				if (!KeyHandler.isDown(uniqueCombo[i]))
					return;
			
			_combosDown.push(keyCombo);
			if (_eOnComboDown) _eOnComboDown.fire(new KeyComboEventArgs(keyCombo));
		} 
		
		/**
		 * Sets a key combination to trigger a <code>onKeyCombo</code> event.
		 * @param pKeyCombo: A defined {@link KeyCombo} object.
		 */

		public static function addKeyCombo(pKeyCombo:KeyCombo):void {
			var l:uint = _combos.length;
			while (l--)
				if (_combos[l].equals(pKeyCombo))
					return;
			
			_longestCombo = Math.max(_longestCombo, pKeyCombo.keyCodes.length);
			_combos.push(pKeyCombo);
		}
		
		/**
		 * Removes a key combination from triggering a <code>onKeyCombo</code> event.
		 * @param keyCombo: A defined {@link KeyCombo} object. 
		 */
		public static function removeKeyCombo(keyCombo:KeyCombo):void {
			var i:int  = -1,
			l:uint = _combos.length;
			
			while (l--) {
				if (_combos[l].equals(keyCombo)) {
					i = int(l);
					break;
				}
			}
			
			if (i == -1)
				return;
			
			_combos.splice(i, 1);
			
			if (keyCombo.keyCodes.length == _longestCombo) {
				var size:uint = 0;
				
				l = _combos.length;
				while (l--)
					size = Math.max(size, _combos[l].keyCodes.length);
				
				_longestCombo = size;
			}
		}
		
		protected function _onDeactivate(e:Event):void {
			var l:uint = _combosDown.length;
			while (l--) {
				if (_eOnComboUp) _eOnComboUp.fire(new KeyComboEventArgs(_combosDown[l]));
			}
			
			_combosDown = new Vector.<KeyCombo>();
		}
		
		private var _eOnComboDown:EventHandler;
		public function get onComboDown():EventHandler{
			if (_eOnComboDown == null) _eOnComboDown = new EventHandler(this);
			return _eOnComboDown;
		}
		
		private var _eOnComboUp:EventHandler;
		public function get onComboUp():EventHandler{
			if (_eOnComboUp == null) _eOnComboUp = new EventHandler(this);
			return _eOnComboUp;
		}
		
		private var _eOnComboSequence:EventHandler;
		public function get onComboSequence():EventHandler{
			if (_eOnComboSequence == null) _eOnComboSequence = new EventHandler(this);
			return _eOnComboSequence;
		}
	}
}
